﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;

using SmlEngine.Sprites.Base;
using SmlEngine.Sprites.Camera;
using SmlEngine.Sprites.Collections;
using SmlEngine.Sprites.Settings;
using SmlEngine.Graphics;
using SmlEngine.UI.Managers;
using SmlEngine.Sprites.Base.WorldMap;
using SmlEngine.Extensions;
using SmlEngine.IO;

namespace SmlEngine.Sprites.Collections
{
    public class World : IName
    {
        #region Properties and Fields

        public string Name { get; set; }

        public InputManager Input { get; set; }

        public IList<Layer> Layers { get; private set; }
        public List<WorldPlayer> Players { get; private set; }

        public Camera2D Camera { get; private set; }

        public int Section { get; set; }

        public int Score { get; set; }

        public bool IsRunning { get; set; }
        private bool isContentLoaded;

        private Vector2 windowSize;

        private int f = 0;
        public int focusedPlayer
        {
            get
            {
                return f;
            }
            set
            {
                LevelDisplayText = "";
                f = value;
            }
        }

        public bool IsLoaded { get; private set; }

        private SpriteFont testFont;

        private Song worldMusic; // temporary
        private SoundEffect jumpSound; // temporary

        #region World Settings
        public string worldName { get; private set; }
        public string worldAuthor { get; private set; }
        // public object Project <-- will be implemented later
        public Vector2 worldOrigin { get; private set; }
        public Vector2 worldSize { get; private set; }
        public string worldFolder { get; private set; }
        #endregion

        #region Jumping
        private bool IsJumping = false;
        private Vector2 jumpDistancePerFrame;
        private int jumpedFor = 0;
        #endregion

        #region Object Lists
        public Dictionary<Vector2, List<WorldMapTile>> staticTiles = new Dictionary<Vector2,List<WorldMapTile>>();
        public Dictionary<Vector2, List<WorldAnimatedTile>> animatedTiles = new Dictionary<Vector2,List<WorldAnimatedTile>>();
        public QuadTree<PathTile> pathTiles = new QuadTree<PathTile>(new Vector2(64, 64));
        public Dictionary<Vector2, List<LevelTile>> levelTiles = new Dictionary<Vector2, List<LevelTile>>();
        public Dictionary<Vector2, List<PipeTile>> pipeTiles = new Dictionary<Vector2, List<PipeTile>>();
        #endregion

        #region Section Data
        public int SwapSectionIndex { get; set; }
        public Vector2 SwapDestination { get; set; }
        public int SwapWorldPlayerIndex { get; set; }

        public bool isCameraLocked;

        private int fadeStep = 0; // used to keep tracking of fading in or out during section swaps.  1 is fading out, -1 is fading in, 0 is neutral
        private int fadeLevel = 60; // 0 is no fade, 60 is all fade

        public event FadeCompletedEventHandler fadeCompletedEvent;
        #endregion

        #region Preloaded Custom Tile Lists
        private Dictionary<string, WorldMapTile> loadedCustomTiles = new Dictionary<string, WorldMapTile>();
        private Dictionary<string, WorldAnimatedTile> loadedCustomAnimTiles = new Dictionary<string, WorldAnimatedTile>();
        private Dictionary<string, LevelTile> loadedCustomLevelTiles = new Dictionary<string, LevelTile>();
        private Dictionary<string, PathTile> loadedCustomPathTiles = new Dictionary<string, PathTile>();
        #endregion

        #region Exit Settings
        private int exitIndex; // how many exits are loaded
        private Dictionary<int, List<PathTile>> pathsByExit = new Dictionary<int,List<PathTile>>(); // stores references to path tiles by exit number
        private int revealingExitIndex = -3; // which exit is being revealed, -3 is the code for no exit is being revealed
        private int revealingTileIndex = -1; // which tile in the exit is being revealed
        private LevelTile currentLevel; // which level tile is currently entered
        private bool unlockCurrentTile; // whether we should unlock the current tile
        #endregion

        public string LevelDisplayText = "";
        public string CoinDisplayText
        {
            get
            {
                return Players[focusedPlayer].Coins.ToString();
            }
        }
        public string LivesDisplayText
        {
            get
            {
                if (focusedPlayer < Players.Count)
                {
                    return Players[focusedPlayer].Lives.ToString();
                }
                else return "";
            }
        }

        public event LevelEnteredEventHandler LevelEntered;
        private LevelEnteredEventArgs eventArgs;

        private bool IsFading
        {
            get
            {
                return fadeLevel != 0 || fadeLevel != 60;
            }
        }

        #endregion

        #region Methods

        #region Constructors

        public World(Vector2 c_windowSize)
        {
            Camera = new Camera2D();
            Layers = new List<Layer>();
            Players = new List<WorldPlayer>();
            windowSize = c_windowSize;

            SwapSectionIndex = -1;
        }
        
        #endregion

        #region Loading
        public void Load(string world_path, ContentManager cm, GraphicsDevice gd)
        {
            if (IsLoaded) return;
            if (File.Exists(world_path))
            {
                worldFolder = world_path.Substring(0, world_path.LastIndexOf(@"\"));
                DataReader dataReader = new DataReader(world_path);
                if (!dataReader.SectionExists("[world]")) { throw new Exception("The world file at " + world_path + " is corrupt."); }

                #region Header
                var header = dataReader.ReadFullSection("[world]");
                if (header == null) throw new Exception(string.Format("The world file at {0} is corrupt or invalid.", world_path));

                worldName = header["name"];
                worldAuthor = header["author"];
                // worldProject = <-- will be implemented later
                worldOrigin = GetVector(header["start"]);
                worldSize = GetVector(header["size"]);
                #endregion

                #region Static Tiles
                dataReader.SetIndexToSection("[static]");
                string[] tile_entry;
                int tile_id;
                string tile_filePath = "";
                int tileX;
                int tileY;
                Vector2 quadTreePos;

                while ((tile_entry = dataReader.ReadNextEntry()) != null)
                {
                    if (!Int32.TryParse(tile_entry[0], out tile_id)) // If this tile is custom
                    {
                        tile_filePath = String.Concat(worldFolder, @"\", tile_entry[1]);
                        tile_id = -1;
                    }
                    tileX = int.Parse(tile_entry[1]);
                    tileY = int.Parse(tile_entry[2]);

                    WorldMapTile lt, t;
                    if (tile_id != -1)
                    {
                        t = WorldObjectSet.LoadWorldMapTile(tile_id, gd, cm);
                    }
                    else
                    {
                        // Custom tile
                        if (!IsCustomTileLoaded(tile_filePath))
                        {
                            lt = WorldObjectSet.LoadCustomStaticTile(worldFolder, tile_filePath, gd);
                            loadedCustomTiles.Add(tile_filePath, lt);
                        }
                        t = (WorldMapTile)loadedCustomTiles[tile_filePath].Clone();
                    }
                    t.SetData(new Vector2(tileX, tileY));
                    quadTreePos = t.location.FloorDivide(64);
                    if (!staticTiles.ContainsKey(quadTreePos)) { staticTiles.Add(quadTreePos, new List<WorldMapTile>()); }
                    staticTiles[quadTreePos].Add(t);
                }


                // Clearing out the (pre-)loaded tiles
                loadedCustomTiles.Clear();
                #endregion

                #region Animated Tiles
                dataReader.SetIndexToSection("[anim]");
                    string[] atile_entry;
                    int atile_id;
                    int atileX;
                    int atileY;
                    string a_filePath = "";

                    while ((atile_entry = dataReader.ReadNextEntry()) != null)
                    {
                        if (!Int32.TryParse(atile_entry[0], out atile_id)) // Custom tile
                        {
                            a_filePath = String.Concat(worldFolder, @"\", atile_entry[1]);
                            atile_id = -1;
                        }
                        atileX = int.Parse(atile_entry[1]);
                        atileY = int.Parse(atile_entry[2]);

                        WorldAnimatedTile a = null, la;
                        if (atile_id >= 0)
                        {
                            a = WorldObjectSet.LoadWorldAnimatedTile(atile_id, gd, cm);
                        }
                        else
                        {
                            if (!IsCustomAnimatedTileLoaded(a_filePath))
                            {
                                la = WorldObjectSet.LoadCustomAnimatedTile(worldFolder, a_filePath, gd);
                                loadedCustomAnimTiles.Add(a_filePath, la);
                            }
                            a = (WorldAnimatedTile)loadedCustomAnimTiles[a_filePath].Clone();
                        }

                        a.SetData(new Vector2(atileX, atileY));
                        quadTreePos = a.Position.FloorDivide(64);
                        if (!animatedTiles.ContainsKey(quadTreePos)) { animatedTiles.Add(quadTreePos, new List<WorldAnimatedTile>()); }
                        animatedTiles[quadTreePos].Add(a);
                    }
                    loadedCustomAnimTiles.Clear();
                #endregion

                #region Path Tiles
                string[] path_entry;
                int path_id;
                string path_cconfig = "";
                int path_X = 0;
                int path_Y = 0;
                int path_exitNumber = 0;
                Vector2 p_quadTreePos;

                dataReader.SetIndexToSection("[path]");
                while ((path_entry = dataReader.ReadNextEntry()) != null)
                {
                    path_exitNumber = 0;
                    if (!Int32.TryParse(path_entry[0], out path_id))
                    {
                        path_cconfig = worldFolder + @"\" + path_entry[2];
                        path_id = -1;
                    }

                    path_X = Int32.Parse(path_entry[1]);
                    path_Y = Int32.Parse(path_entry[2]);
                    path_exitNumber = Int32.Parse(path_entry[3]);

                    PathTile p = null, lp;
                    if (path_id != -1)
                    {
                        p = WorldObjectSet.LoadPathTile(path_id, gd, cm);
                    }
                    else
                    {
                        if (!IsCustomPathTileLoaded(path_cconfig))
                        {
                            lp = WorldObjectSet.LoadCustomPathTile(worldFolder, path_cconfig, gd);
                            loadedCustomPathTiles.Add(path_cconfig, lp);
                        }
                        p = (PathTile)loadedCustomPathTiles[path_cconfig].Clone();
                    }
                    p.SetData(new Vector2(path_X, path_Y), path_exitNumber);
                    p_quadTreePos = p.Position.FloorDivide(64);
                    pathTiles.Add(p);
                    if (!pathsByExit.ContainsKey(path_exitNumber)) { pathsByExit.Add(path_exitNumber, new List<PathTile>()); }
                    pathsByExit[path_exitNumber].Add(p);
                }
                #endregion

                #region World Players
                dataReader.SetIndexToSection("[player]");
                    string[] player_entry;
                    int player_ID;
                    string config_path = "";
                    int player_X;
                    int player_Y;
                    while ((player_entry = dataReader.ReadNextEntry()) != null)
                    {
                        if (!Int32.TryParse(player_entry[0], out player_ID)) // custom player
                        {
                            config_path = player_entry[0];
                            player_ID = -1;
                        }
                        player_X = Int32.Parse(player_entry[1]);
                        player_Y = Int32.Parse(player_entry[2]);

                        WorldPlayer p = null;
                        if (player_ID >= 0)
                        {
                            p = WorldObjectSet.LoadWorldPlayer(player_ID, gd, cm);
                        }
                        else
                        {
                            p = WorldObjectSet.LoadCustomWorldPlayer(worldFolder, config_path, gd);
                        }
                        p.Position = new Vector2(player_X, player_Y);
                        Players.Add(p);
                    }
                #endregion

                #region Level Tiles
                    var tiles = dataReader.ReadFullMultiSection("[level]");
                    if (tiles != null)
                    {
                        foreach (var tile in tiles)
                        {
                            #region Data Loading
                            int id;
                            string configPath = "";
                            Vector2 position;
                            bool revealed;
                            string filePath;
                            string displayName;
                            int exitNumber;
                            string[] exitData;
                            List<Exit> level_exits = new List<Exit>();
                            int revealOn;

                            bool isSMB3Style = false;

                            if (!Int32.TryParse(tile["id"], out id))
                            {
                                configPath = worldFolder + @"\" + tile["id"];
                                id = -1;
                            }
                            position = GetVector(tile["position"]);
                            revealed = tile["reveal"] == "yes" ? true : false;
                            filePath = tile["path"];
                            displayName = tile["display"];
                            exitNumber = Int32.Parse(tile["exits"]);
                            if (tile["exitdata"] != "smb3")
                            {
                                exitData = tile["exitdata"].Split(',');
                                if (exitData.Length > 4) throw new Exception(string.Format("There were too many defined exits for this level tile (level tile #{0})", tiles.IndexOf(tile)));
                                foreach (string s in exitData)
                                {
                                    level_exits.Add(new Exit(s));
                                }
                            }
                            else
                            {
                                isSMB3Style = true;
                            }
                            revealOn = int.Parse(tile["revealOn"]);
                            #endregion
                            LevelTile l = null;
                            if (id >= 0)
                            {
                                    l = WorldObjectSet.LoadLevelTile(id, gd, cm);
                            }
                            else
                            {
                                if (!IsCustomLevelTileLoaded(configPath))
                                {
                                    if (!isSMB3Style)
                                    {
                                        loadedCustomLevelTiles.Add(configPath, WorldObjectSet.LoadCustomLevelTile(worldFolder, configPath, gd));
                                    }
                                    else
                                    {
                                        loadedCustomLevelTiles.Add(config_path, WorldObjectSet.LoadCustomSMB3LevelTile(worldFolder, configPath, gd));
                                    }
                                    l = (LevelTile)loadedCustomLevelTiles[configPath].Clone();
                                }
                            }
                            if (!isSMB3Style)
                            {
                                l.SetData(position, "", filePath, level_exits);
                                l.SetData(revealOn);
                            }
                            else
                            {
                                (l as SMB3LevelTile).SetData(position, "", filePath);
                            }
                            l.LevelName = displayName;
                            Vector2 quadtreePos = l.Position.FloorDivide(64);
                            if (!levelTiles.ContainsKey(quadtreePos)) levelTiles.Add(quadtreePos, new List<LevelTile>());
                            levelTiles[quadtreePos].Add(l);
                            if (!pathsByExit.ContainsKey(revealOn)) pathsByExit.Add(revealOn, new List<PathTile>());
                            pathsByExit[revealOn].Add(l);
                        }
                    }
                #endregion

                #region Pipe Tiles
                    var pipes = dataReader.ReadFullMultiSection("[pipe]");
                    if (pipes != null)
                    {
                        int pipeID;
                        string pipeCustomConfigPath = "";
                        Vector2 pipePosition;
                        string pipeDisplayName;
                        int pipeDestinationSection;
                        Vector2 pipeDestinationPosition;
                        int pipeRevealOnExit;

                        foreach (var pipe in pipes)
                        {
                            if (!int.TryParse(pipe["id"], out pipeID))
                            {
                                pipeCustomConfigPath = pipe["id"];
                                pipeID = -1;
                            }
                            pipePosition = GetVector(pipe["position"]);
                            pipeDisplayName = pipe["display"];
                            pipeDestinationSection = int.Parse(pipe["section"]);
                            pipeDestinationPosition = GetVector(pipe["destination"]);
                            pipeRevealOnExit = int.Parse(pipe["revealOn"]);

                            PipeTile p = null;
                            if (pipeID >= 0)
                            {
                                p = WorldObjectSet.LoadPipeTile(pipeID, gd, cm);
                            }
                            else
                            {
                                p = WorldObjectSet.LoadCustomPipeTile(worldFolder, pipeCustomConfigPath, gd);
                            }

                            p.SetData(pipePosition, pipeRevealOnExit);
                            p.DisplayName = pipeDisplayName;
                            p.DestinationWorldSection = pipeDestinationSection;
                            p.DestinationPosition = pipeDestinationPosition;

                            if (!pipeTiles.ContainsKey(p.tilePosition.FloorDivide(4)))
                            {
                                pipeTiles.Add(p.tilePosition.FloorDivide(4), new List<PipeTile>());
                            }
                            pipeTiles[p.tilePosition.FloorDivide(4)].Add(p);
                            if (!pathsByExit.ContainsKey(p.exitNumber))
                            {
                                pathsByExit.Add(p.exitNumber, new List<PathTile>());
                            }
                            pathsByExit[p.exitNumber].Add(p);
                        }
                    }
                #endregion

                IsLoaded = true;
            }
            else
            {
                throw new FileNotFoundException(string.Format("The world file at {0} could not be found.", world_path));
            }
        }

        public void LoadContent(ContentManager Content, GraphicsDevice Graphics)
        {
            if (!isContentLoaded)
            {
                foreach (var kt in staticTiles)
                {
                    kt.Value.ForEach(t => t.LoadContent(Graphics, Content));
                }
                foreach (var ka in animatedTiles)
                {
                    ka.Value.ForEach(a => a.LoadContent(Graphics, Content));
                }
                foreach (var path in pathTiles)
                {
                    path.LoadContent(Content, Graphics);
                }
                foreach (var kl in levelTiles)
                {
                    foreach (LevelTile l in kl.Value)
                    {
                        if (l is SMB3LevelTile)
                        {
                            (l as SMB3LevelTile).LoadContent(Graphics, Content);
                        }
                        else
                        {
                            l.LoadContent(Graphics, Content);
                        }
                    }
                }
                foreach (var ki in pipeTiles)
                {
                    ki.Value.ForEach(pipe => pipe.LoadContent(Graphics, Content));
                }
                foreach (WorldPlayer player in Players)
                {
                    player.LoadContent(Graphics, Content);
                }

                testFont = Content.Load<SpriteFont>(@"world\testFont");
                worldMusic = Content.Load<Song>(@"sound_test\music");
                jumpSound = Content.Load<SoundEffect>(@"sound_test\smw_scroll");
                MediaPlayer.Volume = 0.5f;
                MediaPlayer.Play(worldMusic);
                isContentLoaded = true;
            }
        }

        private string GetValue(string input)
        {
            return input.Split('=')[1].TrimStart();
        }

        // Okay, this one's a bit complicated.  Basically, pass it in two arrays and some data to search for,
        // and it'll return the index that matches the input for both arrays (assuming they're ordered properly).
        private object GetMatchingObjectByIndex(object[] inputs, object[] outputs, string input)
        {
            if (inputs.Length != outputs.Length) throw new ArgumentException("Could not get matching object by index, parameters were not the same length.");
            int index = Array.IndexOf(inputs, input);
            if (index == -1) return null;
            return outputs[index];
        }

        private static Vector2 GetVector(string input)
        {
            String[] v = input.Split(',');
            v[0] = v[0].Trim();
            return new Vector2(float.Parse(v[0]), float.Parse(v[1]));
        }

        private bool IsCustomTileLoaded(string file_path)
        {
            return loadedCustomAnimTiles.ContainsKey(file_path);
        }

        private bool IsCustomAnimatedTileLoaded(string file_path)
        {
            return loadedCustomAnimTiles.ContainsKey(file_path);
        }

        private bool IsCustomPathTileLoaded(string path)
        {
            return loadedCustomPathTiles.ContainsKey(path);
        }

        private bool IsCustomLevelTileLoaded(string path)
        {
            return loadedCustomLevelTiles.ContainsKey(path);
        }

        #endregion

        #region Update and Draw
        public void UpdateCamera()
        {
            if (!isCameraLocked)
            {
                if (IsJumping)
                {
                    if (jumpedFor == 30)
                    {
                        IsJumping = false;
                        jumpDistancePerFrame = Vector2.Zero;
                        jumpedFor = 0;
                    }
                    Camera.Position += jumpDistancePerFrame;
                    jumpedFor++;
                }
                else
                {
                    Vector2 halfPlayer = new Vector2(Players[focusedPlayer].Size.X / 2, Players[focusedPlayer].Size.Y / 2);
                    Vector2 halfWindow = windowSize / 2;
                    Camera.Position = Players[focusedPlayer].Position + halfPlayer;
                    Camera.Position = new Vector2(Camera.Position.X - halfWindow.Y, Camera.Position.Y - halfWindow.X);  //  I know it looks wrong, but it seems like X and Y are two different things for Camera.Position and halfWindow.
                }
            }
        }

        public void Update(GameTime gameTime)
        {
            UpdateCamera();

            #region Fading Checks
            if (fadeStep < 0) // fade in
            {
                if (fadeLevel > 0) fadeLevel += fadeStep;
                else
                {
                    fadeStep = 0;
                    eventArgs = null;
                    if (revealingExitIndex >= 0)
                    {
                        internalStartReveal(revealingExitIndex);
                    }
                    if (unlockCurrentTile)
                    {
                        internalUnlockTile();
                        unlockCurrentTile = false;
                    }
                    OnFadeCompleted(new FadeCompletedEventArgs(FadeType.In));
                }
            }
            else if (fadeStep > 0) // fade out
            {
                if (fadeLevel < 60) fadeLevel += fadeStep;
                else
                {
                    fadeStep = 0;
                    if (eventArgs != null) OnLevelEntered(eventArgs);
                    OnFadeCompleted(new FadeCompletedEventArgs(FadeType.Out));
                }
            }
            #endregion

            #region Reveal Checks
                if (revealingExitIndex != -3 && revealingTileIndex >= 0) // if we're revealing
                {
                    if (pathsByExit[revealingExitIndex][revealingTileIndex].IsRevealed) // if this tile is done revealing and we're not at the end of the list
                    {
                        revealingTileIndex++;
                        pathsByExit[revealingExitIndex][revealingTileIndex].Reveal();
                    }
                    else if (revealingTileIndex == pathsByExit[revealingExitIndex].Count - 1)
                    {
                        revealingExitIndex = -3;
                        revealingTileIndex = -1;
                    }
                }
            #endregion

            #region Tile Updating
                pathTiles.Update(gameTime);

            foreach (var vl in levelTiles)
            {
                foreach (LevelTile l in vl.Value)
                {
                    l.Update(gameTime);
                }
            }

            foreach (var vi in pipeTiles)
            {
                vi.Value.ForEach(pipe => pipe.Update(gameTime));
            }
            #endregion

            if (Players.Any())
            {
                PathTile currentPath = GetPath(Players[focusedPlayer].tilePosition);
                if (LevelDisplayText == "" && (currentPath != null && currentPath is LevelTile))
                {
                    LevelDisplayText = (currentPath as LevelTile).LevelName;
                }
                else if (LevelDisplayText == "" && (currentPath != null && currentPath is PipeTile))
                {
                    LevelDisplayText = (currentPath as PipeTile).DisplayName;
                }
                else if (currentPath == null || ((!(currentPath is LevelTile)) && (!(currentPath is PipeTile))))
                {
                    LevelDisplayText = "";
                }
            }

            #region Input Handling
            if (!Players.Any()) return;

            if (!Players[focusedPlayer].IsMoving && !IsJumping && fadeStep == 0 && revealingExitIndex == -3)
            {
                var keymap = Input.Current.KeyboardState;
                Vector2 tilePos = Players[focusedPlayer].tilePosition;
                Vector2 distance = Vector2.Zero;

                if (keymap.IsKeyDown(Keys.Up)) { distance = GetDistanceToNextPathStop(Players[focusedPlayer].tilePosition, Direction.Top); Players[focusedPlayer].Move(distance); }
                else if (keymap.IsKeyDown(Keys.Down)) { distance = GetDistanceToNextPathStop(Players[focusedPlayer].tilePosition, Direction.Bottom); Players[focusedPlayer].Move(distance); }
                else if (keymap.IsKeyDown(Keys.Left)) { distance = GetDistanceToNextPathStop(Players[focusedPlayer].tilePosition, Direction.Left); Players[focusedPlayer].Move(distance); }
                else if (keymap.IsKeyDown(Keys.Right)) { distance = GetDistanceToNextPathStop(Players[focusedPlayer].tilePosition, Direction.Right); Players[focusedPlayer].Move(distance); }

                if (Input.IsNewKeyPress(Keys.J) && !IsJumping) Jump();
            }

            if (Input.IsNewKeyPress(Keys.X) && fadeLevel == 0 && !IsJumping)
            {
                PathTile p = GetPath(Players[focusedPlayer].tilePosition);
                if (p is LevelTile)
                {
                    LevelEnteredEventArgs e = new LevelEnteredEventArgs((p as LevelTile).LevelFileName, (p as LevelTile).GetExitIndexes());
                    eventArgs = e;
                    currentLevel = (LevelTile)p;
                    FadeOut();
                }
                else if (p is PipeTile && !Players[focusedPlayer].IsMoving)
                {
                    PipeTile pipe = (PipeTile)p;
                    SwapSectionIndex = pipe.DestinationWorldSection;
                    SwapDestination = pipe.DestinationPosition;
                    SwapWorldPlayerIndex = focusedPlayer;

                    if (SwapSectionIndex == Section) // same world
                    {
                        Vector2 oldPosition = Players[focusedPlayer].Position;
                        Players[focusedPlayer].Position = SwapDestination;
                        SwapSectionIndex = -1;
                        Jump(oldPosition, Players[focusedPlayer].Position);
                    }
                }
            }

            // testing fade
            if (Input.IsNewKeyPress(Keys.A)) FadeIn();
            else if (Input.IsNewKeyPress(Keys.Z)) FadeOut();
            foreach (WorldPlayer p in Players) p.Update(gameTime);
            #endregion
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            spriteBatch.End();
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, Camera.GetTransformation());
            foreach (var vt in staticTiles)
            {
                foreach (WorldMapTile t in vt.Value)
                {
                    t.Draw(gameTime, spriteBatch);
                }
            }

            foreach (var va in animatedTiles)
            {
                foreach (WorldAnimatedTile a in va.Value)
                {
                    a.Draw(gameTime, spriteBatch);
                }
            }

            pathTiles.Draw(gameTime, spriteBatch);

            foreach (var vl in levelTiles)
            {
                foreach (LevelTile l in vl.Value)
                {
                    l.Draw(gameTime, spriteBatch);
                }
            }

            foreach (var vi in pipeTiles)
            {
                vi.Value.ForEach(i => i.Draw(gameTime, spriteBatch));
            }

            if (!unlockCurrentTile)
            {
                foreach (WorldPlayer p in Players)
                {
                    p.Draw(gameTime, spriteBatch);
                }
            }
            spriteBatch.DrawRectangle(GetScreenBounds(), (Color.Black * (fadeLevel * 0.01667f)));
            //spriteBatch.DrawString(testFont, "Now opening: New Pipe Network", new Vector2(30, 30) + Camera.Position, Color.White);
            spriteBatch.End();
            spriteBatch.Begin();
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            IsRunning = true;
        }

        public void Stop()
        {
            IsRunning = false;
        }

        public void Jump(int playerIndex)
        {
            if (playerIndex < 0 || playerIndex > Players.Count - 1)
            {
                throw new ArgumentOutOfRangeException("playerIndex", string.Format("The world tried to focus a player that did not exist ({0})", playerIndex));
            }

            if (Players[focusedPlayer].Position == Players[playerIndex].Position)
            {
                focusedPlayer = playerIndex;
                return;
            }

            IsJumping = true;
            Vector2 v1 = Players[focusedPlayer].Position;
            Vector2 v2 = Players[playerIndex].Position;
            jumpDistancePerFrame = ((v2 - v1) / 30);
            focusedPlayer = playerIndex;

            Players.ForEach(p => p.Velocity = Vector2.Zero);
            jumpSound.Play();
        }

        public void Jump(Vector2 a, Vector2 b)
        {
            if (a == b) return;
            IsJumping = true;
            jumpDistancePerFrame = ((b - a) / 30);
        }

        public void Jump()
        {
            if (Players.Count == 1) return;
            if (focusedPlayer == Players.Count - 1) Jump(0);
            else Jump(focusedPlayer + 1);
        }

        public void StartReveal(int exitIndex)
        {
            revealingExitIndex = exitIndex;
        }

        private void internalStartReveal(int exitIndex)
        {
            if (exitIndex >= 0 && exitIndex < pathsByExit.Count)
            {
                revealingExitIndex = exitIndex;
                if (pathsByExit[exitIndex].Any())
                {
                    int i;
                    LevelTile l = GetLevelTileByExit(exitIndex, out i);
                    if (l != null && l.Exits[i].Cleared == false)
                    {
                        l.Exits[i] = new Exit(exitIndex, l.Exits[i].ExitDirection, true);
                        revealingTileIndex = 0;
                        pathsByExit[exitIndex][0].Reveal();
                    }
                    else
                    {
                        revealingExitIndex = -3;
                        revealingTileIndex = -1;
                    }
                }
            }
        }

        public void UnlockTile()
        {
            unlockCurrentTile = true;
        }

        private void internalUnlockTile()
        {
            if (currentLevel != null && currentLevel is SMB3LevelTile)
            {
                (currentLevel as SMB3LevelTile).Unlock();
            }
        }

        public WorldPlayer GetPlayer(int index)
        {
            if (index < Players.Count)
            {
                return Players[index];
            }
            return null;
        }

        public void AddPlayer(WorldPlayer player, Vector2 position)
        {
            player.Position = position;
            Players.Add(player);
            focusedPlayer = Players.IndexOf(player);
        }

        public void ClearSwap()
        {
            SwapSectionIndex = -1;
            SwapDestination = new Vector2(float.NaN, float.NaN);
            SwapWorldPlayerIndex = -1;
        }

        public void ReloadContent(GraphicsDevice gd, ContentManager cm)
        {
            UnloadContent();
            foreach (var vt in staticTiles) vt.Value.ForEach(s => s.LoadContent(gd, cm));
            foreach (var va in animatedTiles) va.Value.ForEach(a => a.LoadContent(gd, cm));
            foreach (var p in pathTiles) p.LoadContent(cm, gd);
            foreach (var vl in levelTiles) vl.Value.ForEach(l => l.LoadContent(gd, cm));
            Players.ForEach(pl => pl.LoadContent(gd, cm));
        }

        public void UnloadContent()
        {
            foreach (var v in staticTiles) v.Value.ForEach(s => s.UnloadContent());
            foreach (var v in animatedTiles) v.Value.ForEach(a => a.UnloadContent());
            foreach (var v in pathTiles) v.UnloadContent();
            foreach (var v in levelTiles) v.Value.ForEach(l => l.UnloadContent());
            Players.ForEach(pl => pl.UnloadContent());
        }
        #endregion

        #region Section and Pipe Methods
        private void SetSectionToSwap(int newSectionIndex, Vector2 newDestination, int playerSwapIndex)
        {
            SwapSectionIndex = newSectionIndex;
            SwapDestination = newDestination;
            SwapWorldPlayerIndex = playerSwapIndex;
        }

        public void FinishSwap()
        {
            SwapSectionIndex = -1;
            SwapWorldPlayerIndex = -1;
        }

        public void FadeIn()
        {
            if (fadeLevel != 0)
            {
                fadeStep = -1;
            }
        }

        public void FadeOut()
        {
            if (fadeLevel != 60)
            {
                fadeStep = 1;
            }
        }

        public void UpdateFocusedPlayer()
        {
            if (focusedPlayer >= Players.Count)
            {
                focusedPlayer = Players.Count - 1;
            }
        }

        protected virtual void OnFadeCompleted(FadeCompletedEventArgs e)
        {
            if (fadeCompletedEvent != null)
                fadeCompletedEvent(this, e);
        }
        #endregion

        #region Helper Methods
        public PathTile GetPath(Vector2 tileLocation)
        {
            Vector2 quadTreePos = tileLocation.FloorDivide(4);
            if (pathTiles.ContainsCell(quadTreePos))
            {
                foreach (PathTile p in pathTiles[quadTreePos])
                {
                    if (p.tilePosition == tileLocation) return p;
                    else if (!p.Size.ComparePoint(Players[focusedPlayer].Size) && p.Area.GetIntersectionDepth(Players[focusedPlayer].Area) != Vector2.Zero) return p;
                }
            }
            if (levelTiles.ContainsKey(quadTreePos))
            {
                foreach (LevelTile l in levelTiles[quadTreePos])
                {
                    if (l.tilePosition == tileLocation) return l;
                    else if (!l.Size.ComparePoint(Players[focusedPlayer].Size) && l.Area.GetIntersectionDepth(Players[focusedPlayer].Area) != Vector2.Zero) return l;
                }
            }
            if (pipeTiles.ContainsKey(quadTreePos))
            {
                foreach (PipeTile p in pipeTiles[quadTreePos])
                {
                    if (p.tilePosition == tileLocation) return p;
                    else if (!p.Size.ComparePoint(Players[focusedPlayer].Size) && p.Area.GetIntersectionDepth(Players[focusedPlayer].Area) != Vector2.Zero) return p;
                }
            }
            return null;
        }

        private WorldPlayer GetWorldPlayer(Vector2 tileLocation)
        {
            foreach (var player in Players)
            {
                if (player.tilePosition == tileLocation) return player;
            }
            return null;
        }

        private Vector2 GetDistanceToNextPathStop(Vector2 startPathTilePosition, Direction direction)
        {
            Vector2 offset = Vector2.Zero; // the number of tiles to go by
            Vector2 iteratorPosition = Vector2.Zero;
            PathTile p;

            // first, check if the player is on an SMB3LevelTile, and see if it can move
            if ((p = GetPath(startPathTilePosition)) != null)
            {
                if (p is SMB3LevelTile && (p as SMB3LevelTile).LockState == LockedState.Locked)
                {
                    WorldPlayer player = GetWorldPlayer(startPathTilePosition);
                    if (player != null && player.InvertDirection(player.LastMoveDirection) != direction)
                    {
                        return Vector2.Zero;
                    }
                }
            }



            switch (direction)
            {
                case Direction.Top: // up (Y decreases)
                    if (p.Direction == PathType.Horizontal) // || p.Direction == PathType.CornerTopLeft || p.Direction == PathType.CornerTopRight || p.Direction == PathType.TIntersectionFromBottom)
                    {
                        return Vector2.Zero;
                    }
                    iteratorPosition = new Vector2(startPathTilePosition.X, startPathTilePosition.Y - 1);
                    p = GetPath(iteratorPosition);
                    while (p != null)
                    {
                        if (p.IsNode)
                        {
                            offset.Y -= p.Size.Y;
                            return offset;
                        }
                        if (p is SMB3LevelTile)
                        {
                            offset.Y -= p.Size.Y;
                            return offset;
                        }
                        if (!p.IsRevealed)
                        {
                            return offset;
                        }
                        iteratorPosition.Y--;
                        offset.Y -= p.Size.Y;
                        p = GetPath(iteratorPosition);
                    }
                    break;
                case Direction.Bottom: // down (Y increases)
                    if (p.Direction == PathType.Horizontal) // || p.Direction == PathType.CornerBottomLeft || p.Direction == PathType.CornerBottomRight || p.Direction == PathType.TIntersectionFromTop)
                    {
                        return Vector2.Zero;
                    }
                    iteratorPosition = new Vector2(startPathTilePosition.X, startPathTilePosition.Y + 1);
                    p = GetPath(iteratorPosition);
                    while (p != null)
                    {
                        if (p.IsNode)
                        {
                            offset.Y += p.Size.Y;
                            return offset;
                        }
                        if (p is SMB3LevelTile)
                        {
                            offset.Y += p.Size.Y;
                            return offset;
                        }
                        if (!p.IsRevealed)
                        {
                            return offset;
                        }
                        iteratorPosition.Y++;
                        offset.Y += p.Size.Y;
                        p = GetPath(iteratorPosition);
                    }
                    break;
                case Direction.Left: // left (X decreases)
                    if (p.Direction == PathType.Vertical) // || p.Direction == PathType.CornerBottomLeft || p.Direction == PathType.CornerTopLeft || p.Direction == PathType.TIntersectionFromRight)
                    {
                        return Vector2.Zero;
                    }
                    iteratorPosition = new Vector2(startPathTilePosition.X - 1, startPathTilePosition.Y);
                    p = GetPath(iteratorPosition);
                    while (p != null)
                    {
                        if (p.IsNode)
                        {
                            offset.X -= p.Size.X;
                            return offset;
                        }
                        if (p is SMB3LevelTile)
                        {
                            offset.X -= p.Size.X;
                            return offset;
                        }
                        if (!p.IsRevealed)
                        {
                            return offset;
                        }
                        iteratorPosition.X--;
                        offset.X -= p.Size.X;
                        p = GetPath(iteratorPosition);
                    }
                    break;
                case Direction.Right: // right (X increases)
                    if (p.Direction == PathType.Vertical) //|| p.Direction == PathType.CornerBottomRight || p.Direction == PathType.CornerTopRight || p.Direction == PathType.TIntersectionFromLeft)
                    {
                        return Vector2.Zero;
                    }
                    iteratorPosition = new Vector2(startPathTilePosition.X + 1, startPathTilePosition.Y);
                    p = GetPath(iteratorPosition);
                    while (p != null)
                    {
                        if (p.IsNode)
                        {
                            offset.X += p.Size.X;
                            return offset;
                        }
                        if (p is SMB3LevelTile)
                        {
                            offset.X += p.Size.X;
                            return offset;
                        }
                        if (!p.IsRevealed)
                        {
                            return offset;
                        }
                        iteratorPosition.X++;
                        offset.X += p.Size.X;
                        p = GetPath(iteratorPosition);
                    }
                    break;
            }

            return offset;
        }

        private Rectangle GetScreenBounds()
        {
            return new Rectangle((int)Camera.Position.X, (int)Camera.Position.Y, (int)windowSize.Y, (int)windowSize.X);
        }

        private Vector2? CheckForPathTile(Vector2 position, Direction dir)
        {
            Vector2 startTileSize = GetPath(position.FloorDivide(16)).Size;
            Rectangle r = new Rectangle((int)position.X, (int)position.Y, 1, 1);
            if (dir == Direction.Top) r.Y--;
            else if (dir == Direction.Bottom) r.Y++;
            else if (dir == Direction.Left) r.X--;
            else if (dir == Direction.Right) r.X++;
            Vector2 colRectCell = new Vector2(r.X, r.Y).FloorDivide(64);
            foreach (PathTile p in pathTiles[colRectCell])
            {
                Rectangle pRect = p.Position.ToRectangle(p.Size);
                Vector2 depth = r.GetIntersectionDepth(pRect);
                if (depth != Vector2.Zero) return p.Position;
            }
            foreach (LevelTile l in levelTiles[colRectCell])
            {
                Rectangle pRect = l.Position.ToRectangle(l.Size);
                Vector2 depth = r.GetIntersectionDepth(pRect);
                if (depth != Vector2.Zero) return l.Position;
            }
            return null;
        }

        protected virtual void OnLevelEntered(LevelEnteredEventArgs e)
        {
            if (LevelEntered != null)
                LevelEntered(this, e);
        }
        #endregion

        #region Path Methods

        public void AssignPathToExitByBorder(PathTile p)
        {
            #region Field Initialization
            Dictionary<Direction, Vector2> offset = new Dictionary<Direction, Vector2>();
            offset.Add(Direction.Top, new Vector2(0, -1));
            offset.Add(Direction.Bottom, new Vector2(0, 1));
            offset.Add(Direction.Left, new Vector2(-1, 0));
            offset.Add(Direction.Right, new Vector2(1, 0));

            Dictionary<Direction, List<PathType>> movable_types = new Dictionary<Direction, List<PathType>>();
            movable_types.Add(Direction.Top, new List<PathType>() { PathType.CornerTopLeft, PathType.CornerTopRight, PathType.FourWayIntersection,
                PathType.TIntersectionFromBottom, PathType.TIntersectionFromLeft, PathType.TIntersectionFromRight, PathType.Vertical});
            movable_types.Add(Direction.Bottom, new List<PathType>() { PathType.CornerBottomLeft, PathType.CornerBottomRight, PathType.FourWayIntersection,
                PathType.TIntersectionFromLeft, PathType.TIntersectionFromRight, PathType.TIntersectionFromTop, PathType.Vertical});
            movable_types.Add(Direction.Left, new List<PathType>() { PathType.CornerBottomLeft, PathType.CornerTopLeft, PathType.FourWayIntersection,
                PathType.Horizontal, PathType.TIntersectionFromBottom, PathType.TIntersectionFromRight, PathType.TIntersectionFromTop});
            movable_types.Add(Direction.Right, new List<PathType>() { PathType.CornerBottomRight, PathType.CornerTopRight, PathType.FourWayIntersection,
                PathType.Horizontal, PathType.TIntersectionFromBottom, PathType.TIntersectionFromLeft, PathType.TIntersectionFromTop});

            Dictionary<PathType, List<Direction>> searchDirections = new Dictionary<PathType, List<Direction>>();
            searchDirections.Add(PathType.CornerBottomLeft, new List<Direction>() { Direction.Top, Direction.Right });
            searchDirections.Add(PathType.CornerBottomRight, new List<Direction>() { Direction.Top, Direction.Left });
            searchDirections.Add(PathType.CornerTopLeft, new List<Direction>() { Direction.Bottom, Direction.Right });
            searchDirections.Add(PathType.CornerTopRight, new List<Direction>() { Direction.Bottom, Direction.Left });
            searchDirections.Add(PathType.FourWayIntersection, new List<Direction>() { Direction.Top, Direction.Bottom, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.Horizontal, new List<Direction>() { Direction.Left, Direction.Right });
            searchDirections.Add(PathType.TIntersectionFromBottom, new List<Direction>() { Direction.Bottom, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.TIntersectionFromLeft, new List<Direction>() { Direction.Left, Direction.Top, Direction.Bottom });
            searchDirections.Add(PathType.TIntersectionFromRight, new List<Direction>() { Direction.Right, Direction.Top, Direction.Bottom });
            searchDirections.Add(PathType.TIntersectionFromTop, new List<Direction>() { Direction.Top, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.Vertical, new List<Direction>() { Direction.Top, Direction.Bottom });
            #endregion
            foreach (Direction d in Enum.GetValues(typeof(Direction)))
            {
                if (searchDirections[p.Direction].Contains(d))
                {
                    PathTile check_tile = GetPath(p.tilePosition + offset[d]);
                    if (check_tile != null && movable_types[d].Contains(check_tile.Direction))
                    {
                        p.SetData(check_tile.exitNumber);
                        if (!pathsByExit.ContainsKey(p.exitNumber)) pathsByExit.Add(p.exitNumber, new List<PathTile>());
                        pathsByExit[p.exitNumber].Add(p);
                    }
                }
            }
        }

        public List<PathTile> AssignBorderPathsToExit(PathTile p)
        {
            #region Field Initialization
            Dictionary<Direction, Vector2> offset = new Dictionary<Direction, Vector2>();
            offset.Add(Direction.Top, new Vector2(0, -1));
            offset.Add(Direction.Bottom, new Vector2(0, 1));
            offset.Add(Direction.Left, new Vector2(-1, 0));
            offset.Add(Direction.Right, new Vector2(1, 0));

            Dictionary<Direction, List<PathType>> movable_types = new Dictionary<Direction, List<PathType>>();
            movable_types.Add(Direction.Top, new List<PathType>() { PathType.CornerTopLeft, PathType.CornerTopRight, PathType.FourWayIntersection,
                PathType.TIntersectionFromBottom, PathType.TIntersectionFromLeft, PathType.TIntersectionFromRight, PathType.Vertical});
            movable_types.Add(Direction.Bottom, new List<PathType>() { PathType.CornerBottomLeft, PathType.CornerBottomRight, PathType.FourWayIntersection,
                PathType.TIntersectionFromLeft, PathType.TIntersectionFromRight, PathType.TIntersectionFromTop, PathType.Vertical});
            movable_types.Add(Direction.Left, new List<PathType>() { PathType.CornerBottomLeft, PathType.CornerTopLeft, PathType.FourWayIntersection,
                PathType.Horizontal, PathType.TIntersectionFromBottom, PathType.TIntersectionFromRight, PathType.TIntersectionFromTop});
            movable_types.Add(Direction.Right, new List<PathType>() { PathType.CornerBottomRight, PathType.CornerTopRight, PathType.FourWayIntersection,
                PathType.Horizontal, PathType.TIntersectionFromBottom, PathType.TIntersectionFromLeft, PathType.TIntersectionFromTop});

            Dictionary<PathType, List<Direction>> searchDirections = new Dictionary<PathType, List<Direction>>();
            searchDirections.Add(PathType.CornerBottomLeft, new List<Direction>() { Direction.Top, Direction.Right });
            searchDirections.Add(PathType.CornerBottomRight, new List<Direction>() { Direction.Top, Direction.Left });
            searchDirections.Add(PathType.CornerTopLeft, new List<Direction>() { Direction.Bottom, Direction.Right });
            searchDirections.Add(PathType.CornerTopRight, new List<Direction>() { Direction.Bottom, Direction.Left });
            searchDirections.Add(PathType.FourWayIntersection, new List<Direction>() { Direction.Top, Direction.Bottom, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.Horizontal, new List<Direction>() { Direction.Left, Direction.Right });
            searchDirections.Add(PathType.TIntersectionFromBottom, new List<Direction>() { Direction.Bottom, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.TIntersectionFromLeft, new List<Direction>() { Direction.Left, Direction.Top, Direction.Bottom });
            searchDirections.Add(PathType.TIntersectionFromRight, new List<Direction>() { Direction.Right, Direction.Top, Direction.Bottom });
            searchDirections.Add(PathType.TIntersectionFromTop, new List<Direction>() { Direction.Top, Direction.Left, Direction.Right });
            searchDirections.Add(PathType.Vertical, new List<Direction>() { Direction.Top, Direction.Bottom });
            #endregion
            List<PathTile> result = new List<PathTile>();
            foreach (Direction d in Enum.GetValues(typeof(Direction)))
            {
                if (searchDirections[p.Direction].Contains(d))
                {
                    PathTile check_tile = GetPath(p.tilePosition + offset[d]);
                    if (check_tile != null && movable_types[d].Contains(check_tile.Direction))
                    {
                        check_tile.SetData(p.exitNumber);
                        if (!pathsByExit.ContainsKey(p.exitNumber)) pathsByExit.Add(p.exitNumber, new List<PathTile>());
                        pathsByExit[p.exitNumber].Add(check_tile);
                        result.Add(check_tile);
                    }
                }
            }
            return result;
        }

        public void RebuildExitTree()
        {
            #region Field Initialization
            Dictionary<Direction, Vector2> offset = new Dictionary<Direction, Vector2>();
            offset.Add(Direction.Top, new Vector2(0, -1));
            offset.Add(Direction.Bottom, new Vector2(0, 1));
            offset.Add(Direction.Left, new Vector2(-1, 0));
            offset.Add(Direction.Right, new Vector2(1, 0));
            #endregion
            exitIndex = 0;

            foreach (var vl in levelTiles)
            {
                foreach (LevelTile l in vl.Value)
                {
                    foreach (Exit e in l.Exits)
                    {
                        Exit newExit = new Exit(exitIndex, e.ExitDirection, e.Cleared);
                        if (CheckForPathTile(l.Position, newExit.ExitDirection).HasValue)
                        {
                            GetPath(l.Position + offset[newExit.ExitDirection]).SetData(exitIndex);
                            List<PathTile> pathsToProcess = AssignBorderPathsToExit(GetPath(l.Position + offset[newExit.ExitDirection]));
                            List<PathTile> processedPaths = new List<PathTile>();
                            while (pathsToProcess.Any())
                            {
                                foreach (PathTile p in pathsToProcess)
                                {
                                    if (processedPaths.Contains(p))
                                    {
                                        pathsToProcess.Remove(p);
                                        continue;
                                    }
                                    if (p is LevelTile)
                                    {
                                        p.SetData(newExit.Index);
                                        continue;
                                    }
                                    pathsToProcess.AddRange(AssignBorderPathsToExit(p));
                                    pathsToProcess.Remove(p);
                                    processedPaths.Add(p);
                                }
                            }
                            exitIndex++;
                        }
                        l.Exits[l.Exits.IndexOf(e)] = newExit;
                    }
                }
            }
        }

        private LevelTile GetLevelTileByExit(int exitNumber, out int exitNumberinList)
        {
            foreach (var vl in levelTiles)
            {
                foreach (LevelTile l in vl.Value)
                {
                    foreach (Exit e in l.Exits)
                    {
                        if (e.Index == exitNumber) { exitNumberinList = l.Exits.IndexOf(e); return l; }
                    }
                }
            }
            exitNumberinList = -1;
            return null;
        }

        #endregion

        #endregion
    }

    public delegate void LevelEnteredEventHandler(object sender, LevelEnteredEventArgs e);
}